﻿using DeepEarth.Map.Core;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using System;
using DeepEarth.Core;
using System.Linq;
using DeepEarth.Toolkit.Geometry.Clipping;

#if WINDOWS_PHONE
using DeepEarth.Core.Utilities.WP7;
#endif
namespace DeepEarth.Toolkit.Geometry
{
    public static class ShapeHelper
    {
        public static PathFigure CreateFigure(IEnumerable<Location> locations, Func<Location, Point> locationConverterFunction, Point offset, bool closeFigure)
        {
            PathFigure figure = new PathFigure();

            int index = 0;
            foreach (Location location in locations)
            {
                //Add pixels relative to Layer size.
                System.Windows.Point pixel = locationConverterFunction(location);

                pixel.X += offset.X;
                pixel.Y += offset.Y;

                if (index == 0)
                {
                    figure.StartPoint = pixel;
                }
                else
                {
                    var ls = new System.Windows.Media.LineSegment { Point = pixel };
                    figure.Segments.Add(ls);
                }
                index++;
            }

            figure.IsClosed = closeFigure;

            return figure;
        }

        public static PointCollection CreatePoints(IEnumerable<Location> locations, Func<Location, Point> locationConverterFunction)
        {
            PointCollection pc = new PointCollection();
            foreach (Location location in locations)
            {
                //Add pixels relative to Layer size.
                System.Windows.Point pixel = locationConverterFunction(location);

                pc.Add(pixel);
            }

            return pc;
        }

        // Returns a set of clipped shapes. To clip a polygon, use CreateSafeFigure
        // This is used for continuous line which may leave then later re-enter the clipping boundary. We need to return a set of lines in this case.
        public static IEnumerable<PathFigure> CreateSafeFigures(IEnumerable<Location> locations, Func<Location, Point> locationConverterFunction, bool closeFigure, double maxBound)
        {
            List<PathFigure> figures = new List<PathFigure>();

            if (!locations.Any() || locationConverterFunction == null)
            {
                return figures;
            }

            List<Point> initialPoints = locations.Select(l => locationConverterFunction(l)).ToList();

            List<Tuple<List<Point>, bool>> clippedPoints = new List<Tuple<List<Point>, bool>>();

            if (closeFigure)
            {
                Point firstPoint = initialPoints.First();
                if (firstPoint != initialPoints.Last())
                {
                    initialPoints.Add(new Point(firstPoint.X, firstPoint.Y));
                }

                clippedPoints.Add(new Tuple<List<Point>, bool>(Geometry.Clipping.SimplifedSutherlandHodgman.BoxClip(initialPoints, maxBound), true));
            }
            else
            {
                List<List<Point>> lineSegments = Geometry.Clipping.LineClipper.Clip(initialPoints, maxBound);

                foreach (var segment in lineSegments)
                {
                    clippedPoints.Add(new Tuple<List<Point>, bool>(segment, false));
                }
            }

            foreach (var tuple in clippedPoints)
            {
                PathFigure figure = new PathFigure();

                int figureIndex = 0;
                foreach (Point pt in tuple.Item1)
                {
                    System.Windows.Point pixel = pt;

                    if (figureIndex == 0)
                    {
                        figure.StartPoint = pixel;
                    }
                    else
                    {
                        var ls = new System.Windows.Media.LineSegment { Point = pixel };
                        figure.Segments.Add(ls);
                    }
                    figureIndex++;
                }

                figure.IsClosed = closeFigure;

                figures.Add(figure);
            }

            return figures;
        }

        // Silverlight cannot handle large shapes > 32767 (screen) pixels
        // A shape can be larger than this but render correctly if there is scale transform applied that makes it render less than 
        // 32K screen pixels.
        // If poly returns a single clipped poly. If lines, returns a single clipped line.
        public static PathFigure CreateSafeFigure(IEnumerable<Location> locations, Func<Location, Point> locationConverterFunction, bool closeFigure, double maxBound)
        {
            if (!locations.Any())
            {
                return new PathFigure();
            }

            List<Point> clippedPoints = null;
            List<Point> initialPoints = locations.Select(l => locationConverterFunction(l)).ToList();

            if (closeFigure)
            {
                Point firstPoint = initialPoints.First();
                if (firstPoint != initialPoints.Last())
                {
                    initialPoints.Add(new Point(firstPoint.X, firstPoint.Y));
                }

                clippedPoints = Geometry.Clipping.SimplifedSutherlandHodgman.BoxClip(initialPoints, maxBound);
            }
            else
            {
                List<List<Point>> lineSegments = Geometry.Clipping.LineClipper.Clip(initialPoints, maxBound);

                if (lineSegments.Count == 1)
                {
                    clippedPoints = lineSegments.First();
                }
                else
                {
                    // Combine the segments into one line, insert edges along the boundaries where necessary.
                    // The Goal is to create one single line to assist in rendering (we don't want to add more than one
                    // UI Element (Path) per input line. So we join them up within the clipping rectangle, but along the
                    // edge of the clipping rectange where they will be out of the viewport.

                    // Cannot simply join up each line segment - nore can we temporary convert the line into a 
                    // poly and apply Sutherland Hodgman (because at the end, we wouldn't know which edge(s) to remove
                    // to preserve the appearance of the original line)

                    int index = 0;

                    while (index < lineSegments.Count - 2)
                    {
                        List<Point> currentSegment = lineSegments[index];

                        foreach (Point pt in currentSegment)
                        {
                            clippedPoints.Add(pt);
                        }

                        Point currentLast = currentSegment.Last();
                        List<Point> nextSegment = lineSegments[index + 1];
                        Point nextFirst = nextSegment.First();

                        // We're going to need to add some extra points. 
                        // First determine which points we'll need ...
                        var sum = SumBoundaries(currentLast, nextFirst, maxBound);

                        Point lowerLeft = new Point(-maxBound, maxBound);
                        Point lowerRight = new Point(maxBound, maxBound);
                        Point topRight = new Point(maxBound, -maxBound);
                        Point topLeft = new Point(-maxBound, -maxBound);

                        switch (sum)
                        {
                            case 1: // both a and b have vertices on the left bound
                            case 2: // both bottom
                            case 4: // both right
                            case 8: // both top
                                // where both points share the same boundary, we don't have to insert any new points
                                break;
                            case 3: // left and bottom
                                clippedPoints.Add(lowerLeft);
                                break;
                            case 5: // left and right
                                if (currentLast.X == -maxBound)
                                {
                                    clippedPoints.Add(lowerLeft);
                                    clippedPoints.Add(lowerRight);
                                }
                                else
                                {
                                    clippedPoints.Add(topRight);
                                    clippedPoints.Add(topLeft);
                                }
                                break;
                            case 9: // left and top
                                clippedPoints.Add(topLeft);
                                break;
                            case 6: // bottom and right
                                clippedPoints.Add(lowerRight);
                                break;
                            case 10: // bottom and top
                                if (currentLast.X == -maxBound)
                                {
                                    clippedPoints.Add(topLeft);
                                    clippedPoints.Add(lowerLeft);
                                }
                                else
                                {
                                    clippedPoints.Add(lowerRight);
                                    clippedPoints.Add(topRight);
                                }
                                break;
                            case 12:  //  right and top
                                clippedPoints.Add(topRight);
                                break;
                        }
                    }
                }
            }

            PathFigure figure = new PathFigure();

            int figureIndex = 0;
            foreach (Point pt in clippedPoints)
            {
                System.Windows.Point pixel = pt;

                if (figureIndex == 0)
                {
                    figure.StartPoint = pixel;
                }
                else
                {
                    var ls = new System.Windows.Media.LineSegment { Point = pixel };
                    figure.Segments.Add(ls);
                }
                figureIndex++;
            }

            figure.IsClosed = closeFigure;

            return figure;
        }

        static ushort SumBoundaries (Point a, Point b, double maxBound)
        {
            ushort sum = 0;

            if (a.X == -maxBound || b.X == -maxBound)
            {
                sum += 1;
            }

            if (a.Y == maxBound || b.Y == maxBound)
            {
                sum += 2;
            }

            if (a.X == maxBound || b.X == maxBound)
            {
                sum += 4;
            }

            if (a.Y == -maxBound || b.Y == maxBound)
            {
                sum += 8;
            }
            
            // a and b represent edges of two lines that intersect the clipping boundary
            // By looking at the result we can determine which boundaries are involved

            // 1 : both a and b have vertices on the left bound
            // 3 : left and bottom
            // 5 : left and right
            // 9 : left and top
            // 2 : both bottom
            // 6 : bottom and right
            // 10: bottom and top
            // 4 : both right
            // 12 : right and top
            // 8 : both top
            return sum;
        }
    }
}